use std::iter::{self, successors};

type Number = u64;
type PrimalityResult = bool;
type ExpressionResult = bool;

struct OddComposite(Number);
struct SquareComponent(Number);

type GoldbachSum = Number;
type SquareDouble = Number;

pub fn goldbach_conjecture() -> GoldbachSum {
    find_counterexamples()
        .take(2)
        .collect::<Vec<OddComposite>>()
        .iter()
        .map(|OddComposite(n)| n)
        .sum()
}

fn find_counterexamples() -> impl Iterator<Item = OddComposite> {
    odd_numbers_from(9)
        .filter(|&n| !is_prime(n))
        .map(OddComposite)
        .filter(|composite| !satisfies_goldbach_conjecture(composite))
}

fn odd_numbers_from(start: Number) -> impl Iterator<Item = Number> {
    successors(Some(start), |&n| Some(n + 2))
}

fn is_prime(n: Number) -> PrimalityResult {
    match n {
        0 | 1 => false,
        2 => true,
        n if n % 2 == 0 => false,
        n => !has_odd_factors(n),
    }
}

fn has_odd_factors(n: Number) -> bool {
    odd_numbers_from(3)
        .take_while(|&i| i <= (n as f64).sqrt() as Number)
        .any(|i| n % i == 0)
}

fn satisfies_goldbach_conjecture(OddComposite(n): &OddComposite) -> ExpressionResult {
    square_doubles_until(*n).any(|square_double| {
        let remaining = n.saturating_sub(square_double);
        is_prime(remaining)
    })
}

fn square_doubles_until(limit: Number) -> impl Iterator<Item = SquareDouble> {
    iter::successors(Some(SquareComponent(1)), |SquareComponent(i)| {
        Some(SquareComponent(i + 1))
    })
    .map(|SquareComponent(i)| 2 * i * i)
    .take_while(move |&n| n < limit)
}
